<?php

namespace App\Http\Requests\Order;

use App\Http\Requests\ApiRequest;
use App\Http\Requests\Cart\ProductOptionRequest;
use App\Http\Services\Api\V1\App\Product\OrderService;
use App\Models\Cart\Cart;
use App\Models\Location\Country;
use App\Models\Location\Zone;
use App\Models\Order\Address;
use App\Models\Order\OrderTotal;
use App\Models\Shipping\GeoZone;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\Guard;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;

/**
 * @property string address_id
 * @property string shipping_method
 * @property string payment_method
 * @property string store_id
 * @property string comment
 * @property array total_methods
 * @property array cart_ids
 * @property array payment_minimum_totals
 * @property Collection carts
 */
class OrderStoreRequest extends ApiRequest
{
    use ApiChecker, ProductOptionRequest;

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     * @throws \Exception
     */
    public function rules()
    {
        $shipping_data = $this->getShippingData($this->address_id);
        $payment_methods = $this->getPaymentMethods($shipping_data);
        $shipping_methods = $this->getShippingMethods($shipping_data);

        $codes = $shipping_methods['codes'];
        $geo_zone_id_map = $shipping_methods['geo_zone_id_map'];

        $geo_zone_id = $geo_zone_id_map[$this->shipping_method] ?? GeoZone::GEO_ZONE_ID_DEFAULT;

        return [
            'address_id'                  => 'required|int|min:1|max:' . self::MAX_INT,
            'shipping_method'             => ['required', 'string', Rule::in($codes)],
            'payment_method'              => ['nullable', 'string', Rule::in($payment_methods)],
            'comment'                     => 'nullable|string|max:' . self::MAX_STRING,
            'total_methods'               => 'required|array',
            'total_methods.*.code'        => 'required|string|max:' . self::MAX_STRING,
            'total_methods.*.name'        => 'required|string|max:' . self::MAX_STRING,
            'total_methods.*.geo_zone_id' => 'nullable|in:'.$geo_zone_id,
            'cart_ids'                    => 'required|array',
            'cart_ids.*'                  => 'required|int|min:1',
        ];
    }

    /**
     * @param Validator $validator
     * @throws \Exception
     */
    protected function afterValidator(Validator $validator)
    {
        $this->checkTotalMethods($this->total_methods);

        $this->checkProductCarts();
    }

    /**
     * @param array $total_methods
     */
    private function checkTotalMethods(array $total_methods)
    {
        $total_method_codes = array_column($total_methods, 'code') ?? [];

        $extension_total_methods = (new OrderService)->getTotalMethod();

        $codes = $extension_total_methods->pluck('code');

        $codes->filter(function(string $code) use($total_method_codes){

            $this->checkIsTrue(in_array($code, $total_method_codes), trans('checker.check_product_order_total_method_code'));

        });
    }


    /**
     * @param array $shipping_data
     * @return array
     * @throws \Exception
     */
    private function getPaymentMethods(array $shipping_data)
    {
        $extension_payment_methods = (new OrderService)->getPaymentMethod($shipping_data);

        $this->payment_minimum_totals = $extension_payment_methods->pluck('minimum_total', 'code')->all();

        $codes = $extension_payment_methods->pluck('code')->all();

        return $codes;
    }


    /**
     * @param array $shipping_data
     * @return mixed
     * @throws \Exception
     */
    private function getShippingMethods(array $shipping_data)
    {
        $extension_shipping_methods = (new OrderService)->getShipping($shipping_data);

        $codes = $extension_shipping_methods->pluck('code')->all();
        $geo_zone_id_map = $extension_shipping_methods->pluck('geo_zone_id','code')->all();

        return compact('codes', 'geo_zone_id_map');
    }

    /**
     * @param $address_id
     * @return array
     * @throws \Exception
     */
    private function getShippingData($address_id)
    {
        /** @var Address $address */
        $address = Address::getAddress($address_id);
            $this->checkItemExist($address, trans('order.shipping_address_error'));

        $shipping_data = [
            'country_id' => $address->country_id,
            'zone_id'    => $address->zone_id,
        ];

        return $shipping_data;
    }

    /**
     * @throws \Exception
     */
    private function checkProductCarts()
    {
        $cart_ids = $this->cart_ids;

        $carts = $this->getCarts($cart_ids);

        $carts->each(function(Cart $cart){

            $this->option = $cart->option;
            $this->product_id = $cart->product_id;
            $this->quantity = $cart->quantity;

            $this->checkProductExist();
            $this->checkProductQuantity();
            $this->checkProductOptions();
        });

        $this->carts = $carts;
    }

    /**
     * @param $cart_ids
     * @return mixed
     * @throws \Exception
     */
    private function getCarts($cart_ids)
    {
        $carts = (new Cart)->where('customer_id', Guard::customer()->customer_id)
            ->whereIn('cart_id', $cart_ids)
            ->with('product.productDescription',
                'product.productSpecials',
                'product.productDiscounts')
            ->get();

        $this->checkIsTrue($carts->count() === count($cart_ids), trans('cart.quantity_error'));

        return $carts;
    }
}
